package net;

/**
 *
 * @author Andrea Gilardoni
 */
public class Packet {

    private short encryptionType;
    private DataType dataCode;
    private int dataLength;
    private byte[] data;

    public enum DataType {

        ID((byte) 1), PASSWORD((byte) 2), LOGIN((byte) 3),
        SPECIFICS((byte) 4), JOB((byte) 5), JOB_LIB((byte) 6),
        EXCEPTION((byte) 7), FINISH((byte) 8), END_CONNECTION((byte) 9);

        private DataType(byte b) {
            value = b;
        }

        public final byte value;

        public static DataType findByValue(byte b) {
            DataType t[] = values();

            for (DataType dt : t) {
                if (dt.value == b) {
                    return dt;
                }
            }

            throw new IllegalArgumentException("this value doesn't exist");
        }
    }

    public Packet(short encryptionType, DataType dataCode, byte[] data) {
        this.encryptionType = encryptionType;
        this.dataCode = dataCode;
        this.data = data;
        
        if(data != null){
            dataLength = data.length;
        }
        
    }

    public Packet(byte[] protocol) {

        if (!verifyHash(protocol)) {
            throw new RuntimeException("invalid hash");
        }

        int i = 0;
        encryptionType = (short) ((protocol[i++] << 8) + (protocol[i++]));
        
        dataLength = (protocol[i++] << 24) + (protocol[i++] << 16) + (protocol[i++] << 8) + protocol[i++];
        
        dataCode = DataType.findByValue(protocol[i++]);

        data = new byte[dataLength];

        for (int j = 0; j < dataLength; j++) {
            data[j] = protocol[i++];
        }
    }

    public byte[] toByteArr() {
        byte[] protocol = new byte[2 + 4 + 1 + dataLength + 4];

        int i = 0;

        protocol[i++] = (byte) (encryptionType >> 8);
        protocol[i++] = (byte) (encryptionType & 0xff);

        protocol[i++] = (byte) (dataLength >> 24);
        protocol[i++] = (byte) ((dataLength >> 16) & 0xff);
        protocol[i++] = (byte) ((dataLength >> 8) & 0xff);
        protocol[i++] = (byte) (dataLength & 0xff);

        protocol[i++] = dataCode.value;

        if (data != null) {
            for (int j = 0; j < data.length; j++) {
                protocol[i++] = data[j];

            }
        }

        int hash = generateHash(protocol);

        protocol[i++] = (byte) (hash >> 24);
        protocol[i++] = (byte) ((hash >> 16) & 0xff);
        protocol[i++] = (byte) ((hash >> 8) & 0xff);
        protocol[i++] = (byte) (hash & 0xff);

        return protocol;
    }

    public void debugPacket() {
        byte[] protocol = toByteArr();

        System.out.print("protocol [");
        for (byte b : protocol) {
            System.out.print((b & 0xff) + ", ");
        }
        System.out.print("]");

        System.out.println();
    }

    protected static int generateHash(byte[] protocol) {
        int hash = 7;

        for (int i = 0; i < protocol.length - 4; i++) {
            hash = hash + protocol[i] * 31;
        }

        return hash;
    }
    
    protected static int generateHash(short encryptionType, DataType dataCode, byte[] data) {
        int hash = 7;
        byte b[] =  Utility.shortToBytes(encryptionType);
        
        hash += b[0] * 31;
        hash += b[1] * 31;
        
        b = Utility.intToBytes(data.length);
        
        hash += b[0] * 31;
        hash += b[1] * 31;
        hash += b[2] * 31;
        hash += b[3] * 31;
        hash += dataCode.value * 31;
        
        for (int i = 0; i < data.length - 4; i++) {
            hash = hash + data[i] * 31;
        }

        return hash;
    }
    
    private static boolean verifyHash(byte[] protocol) {
        int hash1 = generateHash(protocol);

        int hash2 = (protocol[protocol.length - 4] << 24) + (protocol[protocol.length - 3] << 16) + (protocol[protocol.length - 2] << 8) + protocol[protocol.length - 1];

        return hash1 == hash2;
    }

    public DataType getDataCode() {
        return dataCode;
    }

    public byte[] getData() {
        return data;
    }

}
